//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//********************************************************* 

using System;
using System.Windows.Forms;
using Microsoft.TridentWordAddIn.Common;
using Microsoft.TridentWordAddIn.ResourceHelper;
using System.Threading;
using Microsoft.TridentWordAddIn.Server;
using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.TridentWordAddIn.Presentation
{
    public partial class RunWorkflowTaskPane : BaseUserControl, ITaskPaneControl, IDisposable
    {
        private bool isCanceled, isError;
        private Thread asyncRerunJob;
        private Thread asyncDownloadWorkflows;
        private Workflow workflow;
        private Guid jobId;
        private string jobName;
        private string exceptionMessage;
        private List<Workflow> workflows;
        private ListViewSorting lvWorkflowsSorting;
        int previouslySortedWorkflowsColumn;

        /// <summary>
        /// Initializes a new instance of the <see cref="RerunWorkflowTaskPane"/> class.
        /// </summary>
        public RunWorkflowTaskPane()
        {
            base.Initialize(true);
            this.InitializeComponent();                       
            
            ControllerInstance.OnConnectionChanged += new EventHandler<ConnectionChangedEventArgs>(ControllerInstance_OnConnectionChanged);
            ControllerInstance.OnDocumentClosing += new EventHandler<BaseEventArgs>(ControllerInstance_OnDocumentClosing);
            this.ctrEditParameters.IsJobPresent = false;
            this.workflows = new List<Workflow>();
            this.InitializeListViewSorting();            
            this.UpdateWorkflowList();
            this.ctrServerSettings.Initialize(true);
        }

        #region Private Methods       
       
        /// <summary>
        /// Updates the workflow list.
        /// </summary>
        private void UpdateWorkflowList()
        {
            this.ctrWorkflowActivities.Refresh(null);
            this.ctrEditParameters.Refresh(null, null);
            this.asyncDownloadWorkflows.CancelThread();            
            this.lvWorkflows.Visible = false;
            this.ctrJobProcessing.Visible = true;
            this.asyncDownloadWorkflows = new Thread(new ThreadStart(this.DownloadWorkflows));
            this.asyncDownloadWorkflows.Start();
            this.timerDownload.Start();
        }

        /// <summary>
        /// Initializes the list view sorting.
        /// </summary>
        private void InitializeListViewSorting()
        {
            this.lvWorkflowsSorting = new ListViewSorting();
            this.lvWorkflows.ListViewItemSorter = this.lvWorkflowsSorting;
            this.previouslySortedWorkflowsColumn = -1;            
        }

        /// <summary>
        /// Downloads the workflows.
        /// </summary>
        private void DownloadWorkflows()
        {
            try
            {
                this.isError = false;
                this.workflows.Clear();
                this.workflows = this.Server.Workflows;
            }
            catch (DatabaseException)
            {
                this.isError = true;
            }
            catch (ConnectionFailedException)
            {
                this.isError = true;
            }
            catch (ThreadAbortException)
            {
            }
            catch (Exception ex)
            {
                this.isError = true;
                if (ex.Message.CompareIgnoreCase("Unknown web service failure"))
                {
                    return;
                }

                this.BeginInvoke(new ShowMessageDelegate(MessageBoxHelper.ShowException), new object[] { ex, this });
            }
        }

        /// <summary>
        /// Downloads the completed.
        /// </summary>
        private void DownloadCompleted()
        {
            this.lvWorkflows.Visible = true;
            this.ctrJobProcessing.Visible = false;

            if (this.isError)
            {
                this.lvWorkflows.ShowErrorMessage(
                    "Error occured during retrieving the workflows. Please check configuration.");
            }
            else
            {
                this.UpdateWorkflowListView();

                bool firstTime = this.previouslySortedWorkflowsColumn == -1;
                int column = firstTime ? 0 : this.previouslySortedWorkflowsColumn;
                if (firstTime)
                {
                    this.lvWorkflowsSorting.SortDirection = SortOrder.Ascending;
                }

                lvWorkflows.SetSortIcon(this.lvWorkflowsSorting, ref this.previouslySortedWorkflowsColumn, column);
                this.lvWorkflows.Sort();
            }
        }

        /// <summary>
        /// Updates the workflow list view.
        /// </summary>
        private void UpdateWorkflowListView()
        {
            this.lvWorkflows.BeginUpdate();
            this.lvWorkflows.Items.Clear();

            if (this.workflows.Count == 0)
            {
                this.lvWorkflows.ShowErrorMessage("No workflows present at selected connection.");
            }
            else
            {
                string[] columns = new string[] { "Workflow", "Description" };
                this.lvWorkflows.AddColumns(columns);
                foreach (Workflow workflow in this.workflows)
                {
                    string workflowName = workflow.DisplayName.IsNullOrEmpty() ? workflow.Name : workflow.DisplayName;
                    ListViewItem newItem = new ListViewItem(workflowName);
                    newItem.ImageKey = "workflow";
                    newItem.SubItems.Add(workflow.Description);
                    newItem.Tag = workflow;
                    this.lvWorkflows.Items.Add(newItem);
                }
            }

            this.lvWorkflows.EndUpdate();
        }

        /// <summary>
        /// Validates the before rerun.
        /// </summary>
        /// <returns>bool</returns>
        private bool ValidateBeforeRerun()
        {
            if (this.workflow == null)
            {
                MessageBoxHelper.ShowDefault(ResourceFetcher.GetString("NoWorkflowSelected"));
                return false;
            }

            if (this.Server.IsWorkflowDeleted(this.workflow.Id))
            {
                MessageBoxHelper.ShowDefault(ResourceFetcher.GetString("WorkflowDeleted"));
                return false;
            }

            if (!this.ctrServerSettings.ValidateSelection())
            {
                return false;
            }

            if (this.ctrEditParameters.ValidateParameters(this.workflow) == false)
            {
                this.tabctrActivity.SelectedIndex = 0;
                MessageBoxHelper.ShowDefault(ResourceFetcher.GetString("IncorrectInput"), MessageBoxIcon.Warning);
                return false;
            }

            return true;
        }

        /// <summary>
        /// Submits the rerun job.
        /// </summary>
        /// <param name="rerunParam">The rerun param.</param>
        private void RunWorkflow(object rerunParam)
        {
            try
            {
                jobId = ControllerInstance.SumbitJob(this.Server, rerunParam as RerunWorkflowInformation);
                if (jobId == Guid.Empty)
                {
                    this.isError = true;
                }
            }
            catch (DatabaseException ex)
            {
                this.isError = true;
                exceptionMessage = ex.Message;
            }
            catch (NoRecordFoundException)
            {
                this.isError = true;
                exceptionMessage = string.Format(ResourceFetcher.GetString("NoWorkflowFound"), workflow.Id);
            }
            catch (ThreadAbortException)
            {
                this.isError = true;
            }
            catch (Exception ex)
            {
                this.isError = true;
                if(ex.Message.CompareIgnoreCase("Unknown web service failure"))
                {
                    return;
                }

                this.BeginInvoke(new ShowMessageDelegate(MessageBoxHelper.ShowException), new object[] { ex, this});
            }
        }

        /// <summary>
        /// Befores the rerun begin.
        /// </summary>
        private void BeforeRunWorkflowBegin()
        {
            this.exceptionMessage = string.Empty;
            this.isCanceled = false;
            this.isError = false;
            this.btnRun.Enabled = false;
            this.btnTerminate.Enabled = true;
            this.splitContainerTop.Enabled = false;
            this.jobName = this.ctrServerSettings.JobName;
            this.ControllerInstance.BeginModelTransaction(DocumentKey);
        }

        /// <summary>
        /// Afters the rerun end.
        /// </summary>
        private void AfterRunWorkflowEnd()
        {
            this.btnRun.Enabled = true;
            this.btnTerminate.Enabled = false;
            this.splitContainerTop.Enabled = true;

            if (this.isCanceled)
            {
                this.ControllerInstance.RollbackModelTransaction(DocumentKey);
                this.toolStriplblStatus.Text = "Job canceled by user";
            }
            else if (this.isError)
            {
                this.ControllerInstance.RollbackModelTransaction(DocumentKey);
                this.toolStriplblStatus.Text = "Job failed!";
                if (exceptionMessage.Length != 0)
                {
                    MessageBoxHelper.ShowDefault(exceptionMessage, MessageBoxIcon.Warning);
                }
            }
            else
            {
                this.ControllerInstance.CommitModelTransaction(DocumentKey);
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the Click event of the btnRerun control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnRerun_Click(object sender, EventArgs e)
        {
            if (!this.ValidateBeforeRerun())
            {
                return;
            }

            string machine = this.ctrServerSettings.MachineName;
            string jobName = this.ctrServerSettings.JobName;
            if (this.Server != null)
            {
                RerunWorkflowInformation rerunParam = new RerunWorkflowInformation(
                    jobName,
                    machine,
                    this.workflow,
                    this.ctrEditParameters.Parameters);

                this.BeforeRunWorkflowBegin();
                this.asyncRerunJob = new Thread(new ParameterizedThreadStart(this.RunWorkflow));
                this.asyncRerunJob.Name = "Submit Job";
                this.asyncRerunJob.SetApartmentState(ApartmentState.STA);
                this.asyncRerunJob.Start(rerunParam);
                this.progressTimer.Start();
            }
        }

        /// <summary>
        /// Handles the Tick event of the progressTimer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void progressTimer_Tick(object sender, EventArgs e)
        {
            bool isRunning = false;
            string status = string.Empty;

            if (jobId != Guid.Empty && !this.isCanceled && !this.isError)
            {
                try
                {
                    status = this.Server.JobState(jobId, out isRunning);
                }
                catch (ConnectionFailedException)
                {
                    this.isError = true;
                    this.progressTimer.Stop();
                    MessageBoxHelper.ShowDefault(ResourceFetcher.GetString("NoConnection"), MessageBoxIcon.Error);
                }
                catch (Exception ex)
                {
                    this.isError = true;
                    this.progressTimer.Stop();
                    MessageBoxHelper.ShowException(ex, null);
                }
            }

            if ((!this.asyncRerunJob.IsAlive && !isRunning) || this.isCanceled || this.isError)
            {
                this.progressTimer.Stop();
                this.AfterRunWorkflowEnd();

                if (this.isError)
                {
                    status = "failed!";
                }

                if (!isCanceled)
                {
                    string displayStatus = this.jobName + " " + status + "!";
                    this.toolStriplblStatus.Text = displayStatus;
                    this.toolStriplblStatus.ToolTipText = displayStatus;
                }
            }
            else if (!isCanceled)
            {
                string displayStatus = this.jobName + " " + status + "...";
                this.toolStriplblStatus.Text = displayStatus;
                this.toolStriplblStatus.ToolTipText = displayStatus;
            }
        }

        /// <summary>
        /// Handles the Click event of the btnCancel control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnTerminate_Click(object sender, EventArgs e)
        {
            if (jobId != Guid.Empty)
            {
                this.Server.TerminateJob(jobId);
                this.btnTerminate.Enabled = false;
            }
        }

        /// <summary>
        /// Handles the Tick event of the timerDownload control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void timerDownload_Tick(object sender, EventArgs e)
        {
            if (!this.asyncDownloadWorkflows.IsAlive)
            {
                this.timerDownload.Stop();
                this.DownloadCompleted();
            }
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the lvWorkflows control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void lvWorkflows_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.lvWorkflows.SelectedIndices.Count > 0)
            {
                workflow = this.lvWorkflows.SelectedItems[0].Tag as Workflow;
                this.ctrWorkflowActivities.Refresh(workflow);
                this.ctrEditParameters.Refresh(workflow);
            }
            else
            {
                this.ctrWorkflowActivities.Refresh(null);
                this.ctrEditParameters.Refresh(null);
            }
        }       

        /// <summary>
        /// Handles the OnConnectionChanged event of the ControllerInstance control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.ConnectionChangedEventArgs"/> instance containing the event data.</param>
        private void ControllerInstance_OnConnectionChanged(object sender, ConnectionChangedEventArgs e)
        {
            if (e.Operation == ConnectionOperation.Deleted && this.Server.CurrentTridentConnection != null)
            {
                return;
            }

            this.UpdateWorkflowList();
        }

        /// <summary>
        /// Handles the OnDocumentClosing event of the ControllerInstance control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Microsoft.TridentWordAddIn.Common.BaseEventArgs"/> instance containing the event data.</param>
        private void ControllerInstance_OnDocumentClosing(object sender, BaseEventArgs e)
        {
            if (e.DocumentKey == DocumentKey)
            {
                this.isCanceled = true;
                this.asyncDownloadWorkflows.CancelThread();
                this.asyncRerunJob.CancelThread();
            }
        }

        /// <summary>
        /// Handles the ColumnClick event of the lvWorkflows control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.ColumnClickEventArgs"/> instance containing the event data.</param>
        private void lvWorkflows_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine if clicked column is already the column that is being sorted.
            if (e.Column == this.lvWorkflowsSorting.SortColumn)
            {
                this.lvWorkflowsSorting.SortDirection = this.lvWorkflowsSorting.SortDirection == SortOrder.Ascending ?
                    SortOrder.Descending : SortOrder.Ascending;
            }
            else
            {
                this.lvWorkflowsSorting.SortColumn = e.Column;
                this.lvWorkflowsSorting.SortDirection = SortOrder.Ascending;
            }

            this.lvWorkflows.SetSortIcon(this.lvWorkflowsSorting, ref this.previouslySortedWorkflowsColumn, e.Column);
            this.lvWorkflows.Sort();
        }

        #endregion

        #region ITaskPaneControl Members

        /// <summary>
        /// Sets the dock position.
        /// </summary>
        /// <param name="dockPosition">The dock position.</param>
        public void SetDockPosition(TaskPaneDockPositions dockPosition)
        {
            try
            {
                if ((dockPosition & (TaskPaneDockPositions.Left | TaskPaneDockPositions.Right | TaskPaneDockPositions.Floating)) != 0)
                {
                    this.SetVertical(true);
                }
                else
                {
                    this.SetVertical(false);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Sets the vertical.
        /// </summary>
        /// <param name="controlIsVertical">if set to <c>true</c> [control is vertical].</param>
        private void SetVertical(bool controlIsVertical)
        {
            this.SuspendLayout();

            int initialSplitterDistance = controlIsVertical ==
                true ? this.splitContainerTop.MinimumSize.Height : this.splitContainerTop.MinimumSize.Width;

            this.splitContainerBottom.SplitterDistance = initialSplitterDistance;
            this.splitContainerMiddle.SplitterDistance = initialSplitterDistance;
            this.splitContainerTop.SplitterDistance = initialSplitterDistance;

            Orientation orientation = controlIsVertical ? Orientation.Horizontal : Orientation.Vertical;

            this.splitContainerTop.Orientation = orientation;
            this.splitContainerMiddle.Orientation = orientation;
            this.splitContainerBottom.Orientation = orientation;

            if (controlIsVertical)
            {
                this.splitContainerComplete.SplitterDistance = (int)(this.Height * 0.88);
                this.splitContainerTop.SplitterDistance = (int)(this.Height * 0.30);
                this.splitContainerMiddle.SplitterDistance = (int)(this.Height * 0.47);
                this.splitContainerBottom.SplitterDistance = (int)(this.Height * 0.15);
            }
            else
            {
                this.splitContainerComplete.FixedPanel = FixedPanel.None;
                int splitterDistance = this.Width / 3;
                this.splitContainerComplete.SplitterDistance = (int)(this.Height * 0.85);
                this.splitContainerTop.SplitterDistance = splitterDistance;
                this.splitContainerMiddle.SplitterDistance = splitterDistance;
                this.splitContainerBottom.SplitterDistance = (int)(this.Width * 0.27);
            }

            this.ResumeLayout();
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Releases all resources used by the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        void IDisposable.Dispose()
        {
            this.Disposing(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes the specified the privately held resources.
        /// </summary>
        /// <param name="disposing">if set to <c>true</c> disposes resources.</param>
        private void Disposing(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.ControllerInstance.OnDocumentClosing -= new EventHandler<BaseEventArgs>(ControllerInstance_OnDocumentClosing);
                    ControllerInstance.OnConnectionChanged -= new EventHandler<ConnectionChangedEventArgs>(ControllerInstance_OnConnectionChanged);
                    base.Disposing();
                }
            }

            this.disposed = true;
        }

        #endregion
      
    }
}
